package com.itheima.config;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.io.FileReader;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;

/**
 * Demo class
 *
 * @author wengkaifang
 * @date 2019/10/31
 */
public class CreateSonByCGLibImpl implements CreateSonByCGLib {

    /**
     * 参数定义,用于存储所有子类除父类的所有方法名字
     */
    private Set<String> allSonMethodNames = new HashSet<String>();

    /**
     * 参数定义,用于支持用户自己指定返回基本类型的返回值
     */
    private Map<String, Object> customDefinedReturnColumMap = new HashMap<String, Object>();

    /**
     * 参数定义,用于用户存储用户自定义的方法和返回对象
     */
    private Map<String, Object> getCustomDefinedMethodReturnColumMap = new HashMap<String, Object>();

    /**
     * 重写方法的存储,用于存储用户重写父类方法的方法名
     */
    private Set<String> inputAllOverrideOrFatherMethodname = new HashSet<String>();

    /**
     * 注入预期指定对象
     * 还没想好留着这货干什么用,就先留着吧
     * @return
     */
    private Object inputObject = new Object();

    /**
     * 主进程,传入子类和父类class对象,传出一个可测试的子类对象
     *
     * @param sonClass
     * @return Object
     */
    @Override
    public Object createTestSonObject(Class sonClass) throws RuntimeException {
        Class fatherClass = sonClass.getSuperclass();
        if (null == sonClass || null == fatherClass) {
            throw new RuntimeException("Do CreateSonByCGLib fail,please check the sonClass or fatherClass is not null!");
        }
        getAllSonMethodName(sonClass, fatherClass);
        splicingMethodName(sonClass);
        readProperties();
        Object sonByCGLib = createSonByCGLib(sonClass);
        return sonByCGLib;
    }

    /**
     * 通过CGL创建子对象
     *
     * @param sonClass
     * @return
     */
    private Object createSonByCGLib(Class sonClass) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(sonClass);
        final Set<String> allCustomDefinedMethod = getCustomDefinedMethodReturnColumMap.keySet();
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                String methodName = method.getName();
                if (null == methodName || "".equals(methodName)) {
                    throw new RuntimeException("Do CreateSonByCGLib fail,please check your Object has a method at least ");
                }
                if(0!=allCustomDefinedMethod.size()&&allCustomDefinedMethod.contains(methodName)){
                    return getCustomDefinedMethodReturnColumMap.get(methodName);
                }
                if (0!=inputAllOverrideOrFatherMethodname.size()&&inputAllOverrideOrFatherMethodname.contains(methodName)){
                    return methodProxy.invokeSuper(o, objects);
                }
                if (!allSonMethodNames.contains(methodName)) {
                    Class<?> returnType = method.getReturnType();
                    return judgeType(returnType);
                }
                return methodProxy.invokeSuper(o, objects);
            }
        });

        return enhancer.create();
    }

    /**
     * 通过读取properties来定义基础类型参数的期望值(需解决相对路径问题)
     */
    private void readProperties() {
        FileReader fr = null;
        Properties prop = new Properties();
        try {
            fr = new FileReader(this.getClass().getResource("/customDefinedReturnColum.properties").getPath());
            prop.load(fr);
            fr.close();
            String inputIndex = (String) prop.get("byte");
            if (null == inputIndex) {
                inputIndex = (String) prop.getProperty("Byte", "0");
            }
            customDefinedReturnColumMap.put("Byte", Byte.valueOf(inputIndex));

            inputIndex = (String) prop.get("short");
            if (null == inputIndex) {
                inputIndex = (String) prop.getProperty("Short", "0");
            }
            customDefinedReturnColumMap.put("Short", Short.valueOf(inputIndex));

            inputIndex = (String) prop.get("int");
            if (null == inputIndex) {
                inputIndex = (String) prop.getProperty("Integer", "0");
            }
            customDefinedReturnColumMap.put("Integer", Integer.valueOf(inputIndex));

            inputIndex = (String) prop.get("long");
            if (null == inputIndex) {
                inputIndex = (String) prop.getProperty("Long", "0");
            }
            customDefinedReturnColumMap.put("Long", Long.valueOf(inputIndex));

            inputIndex = (String) prop.get("float");
            if (null == inputIndex) {
                inputIndex = (String) prop.getProperty("Float", "0.0");
            }
            customDefinedReturnColumMap.put("Float", Float.valueOf(inputIndex));

            inputIndex = (String) prop.get("double");
            if (null == inputIndex) {
                inputIndex = (String) prop.getProperty("Double", "0.0");
            }
            customDefinedReturnColumMap.put("Double", Double.valueOf(inputIndex));

            inputIndex = (String) prop.get("boolean");
            if (null == inputIndex) {
                inputIndex = (String) prop.getProperty("Boolean", "true");
            }
            customDefinedReturnColumMap.put("Boolean", Boolean.valueOf(inputIndex));

            inputIndex = (String) prop.get("char");
            if (null == inputIndex) {
                inputIndex = (String) prop.getProperty("Character", "0");
            }
            customDefinedReturnColumMap.put("Character", inputIndex.charAt(0));

            inputIndex = (String) prop.get("String");
            if (null == inputIndex) {
                inputIndex = "0";
            }
            customDefinedReturnColumMap.put("String", inputIndex);
        } catch (Exception ex) {
            customDefinedReturnColumMap.put("Byte", 0);
            customDefinedReturnColumMap.put("Short", 0);
            customDefinedReturnColumMap.put("Integer", 0);
            customDefinedReturnColumMap.put("Long", 0L);
            customDefinedReturnColumMap.put("Float", 0.0f);
            customDefinedReturnColumMap.put("Double", 0.0);
            customDefinedReturnColumMap.put("Boolean", true);
            customDefinedReturnColumMap.put("Character", '0');
            customDefinedReturnColumMap.put("String", "0");
            System.out.println("System not find file named customDefinedReturnColum.properties , now use default colum");
        } finally {
            customDefinedReturnColumMap.put("Object", inputObject);
        }
    }

    /**
     * 获得属于子类自己方法的所有方法名,并赋予allSonMethodNames变量
     *
     * @param sonClass    子类class文件
     * @param fatherClass 父类class文件
     * @return
     */
    private void getAllSonMethodName(Class sonClass, Class fatherClass) {
        Method[] sonMethods = sonClass.getMethods();
        Method[] fatherMethods = fatherClass.getMethods();
        this.allSonMethodNames = new HashSet<String>();
        Set<String> allFatherMethodNames = new HashSet<String>();
        for (Method sonMethod : sonMethods) {
            allSonMethodNames.add(sonMethod.getName());
        }
        for (Method fatherMethod : fatherMethods) {
            allFatherMethodNames.add(fatherMethod.getName());
        }
        allSonMethodNames.removeAll(allFatherMethodNames);
    }


    /**
     * 父类方法的覆盖,根据返回值类型返回对应的值
     *
     * @param condition
     * @return
     */
    private Object judgeType(Class condition) {
        //8种基本数据类型和工具类
        if (byte.class == condition || Byte.class == condition) {
            return customDefinedReturnColumMap.get("Byte");
        }
        if (short.class == condition || Short.class == condition) {
            return customDefinedReturnColumMap.get("Short");
        }
        if (int.class == condition || Integer.class == condition) {
            return customDefinedReturnColumMap.get("Integer");
        }
        if (long.class == condition || Long.class == condition) {
            return customDefinedReturnColumMap.get("Long");
        }
        if (float.class == condition || Float.class == condition) {
            return customDefinedReturnColumMap.get("Float");
        }
        if (double.class == condition || Double.class == condition) {
            return customDefinedReturnColumMap.get("Double");
        }
        if (boolean.class == condition || Boolean.class == condition) {
            return customDefinedReturnColumMap.get("Boolean");
        }
        if (char.class == condition || Character.class == condition) {
            return customDefinedReturnColumMap.get("Character");
        }
        if (String.class == condition) {
            return customDefinedReturnColumMap.get("String");
        }
        return customDefinedReturnColumMap.get("Object");
    }

    /**
     * 初衷是:由于反射无法获取从父类继承的重写的方法,
     * (查看jdk官方api文档,发现无法获取重写的方法),
     * 所以需要用户自己存入重写的方法的方法名,以免被覆盖
     * 但后来想想可以用于注入用户想保留的所有子类继承父类的方法,就改下名字吧
     * @param inputOverrideOrFatherMethodname 输入重写方法名
     * @return boolean true表示添加成功,flase表示添加失败
     */
    public boolean inputOverrideOrFatherMethodname(String inputOverrideOrFatherMethodname){
        if (null==inputOverrideOrFatherMethodname||"".equals(inputOverrideOrFatherMethodname)){
            System.out.println("The methodname can't be null,please check inputOverrideMethodname");
            return false;
        }
        inputAllOverrideOrFatherMethodname.add(inputOverrideOrFatherMethodname);
        return true;
    }

    /**
     * 编译get/set方法名字
     *
     * @return
     */
    private void splicingMethodName(Class myClass) {
        allSonMethodNames.add("toString");
        Field[] fields = myClass.getDeclaredFields();
        StringBuilder getBuilder = new StringBuilder("get");
        StringBuilder setBuilder = new StringBuilder("set");
        for (Field field : fields) {
            String name = field.getName();
            char[] chars = name.toCharArray();
            chars[0] = Character.toUpperCase(chars[0]);
            for (char aChar : chars) {
                setBuilder.append(aChar);
                getBuilder.append(aChar);
            }
            allSonMethodNames.add(setBuilder.toString());
            allSonMethodNames.add(getBuilder.toString());
            getBuilder = new StringBuilder("get");
            setBuilder = new StringBuilder("set");
        }
    }

    /**
     * 用户自定义方法和放回值
     *
     * @return boolean 方法注入成功返回true,否则回返回false
     */
    @Override
    public <T> boolean setCustomDefinedMethodAndReturnValue(Class sonClass ,String methodName,T returnValue){
        Method[] methods = sonClass.getMethods();
        if (null==methodName||"".equals(methodName)){
            System.out.println("The methodName can't be null , Please check your input methodName!!!");
            return false;
        }
        if (null==returnValue){
            System.out.println("The returnValue can't be null , Please check your input returnValue!!!");
            return false;
        }
        for (Method method : methods) {
            if (returnValue.getClass()==method.getReturnType()&&methodName==method.getName()){
                getCustomDefinedMethodReturnColumMap.put(methodName,returnValue);
                return true;
            }
        }
        System.out.println("When do method \"setCustomDefinedMethodAndReturnValue\" has wrong ,The "+methodName+" has a wrong defined returnValue's type , Please check your input returnValue's type!!!");
        return false;
    }

    /**
     * 还没想好留着这货干什么用,就先留着吧
     * @return
     */
    public Object getInputObject() {
        return inputObject;
    }

    /**
     * 还没想好留着这货干什么用,就先留着吧
     * @return
     */
    public void setInputObject(Object inputObject) {
        this.inputObject = inputObject;
    }

    /**
     * 打印一下这个对象
     * @return
     */
    @Override
    public String toString() {
        return "CreateSonByCGLibImpl{" +
                "allSonMethodNames=" + allSonMethodNames +
                ", customDefinedReturnColumMap=" + customDefinedReturnColumMap +
                ", getCustomDefinedMethodReturnColumMap=" + getCustomDefinedMethodReturnColumMap +
                ", inputAllOverrideOrFatherMethodname=" + inputAllOverrideOrFatherMethodname +
                ", inputObject=" + inputObject +
                '}';
    }
}
